﻿using System;
using System.Linq;
using System.Runtime.InteropServices;

//using Magic;

namespace RoMBot.MemoryLib
{
    public class Memory
    {
        //private static readonly BlackMagic b = new BlackMagic();
        private IntPtr memHandle;
        private IntPtr winHandle;
        internal int ProcId;
        internal static readonly Memory Instance;

        static Memory()
        {
            Instance = new Memory();
        }

        public static IntPtr ProcHandle
        {
            get
            {
                if ( Instance.ProcId == 0 )
                {
                    Instance.ProcId = Bot.Instance.ProcId;
                    Instance.winHandle = SWindow.FindWindowByProcessId( Instance.ProcId );
                    Instance.memHandle = SProcess.OpenProcess( Instance.ProcId );
                    if ( Instance.memHandle == IntPtr.Zero )
                    {
                        throw new Exception( "No se puede establecer la comunicación con el proceso." );
                    }
                }
                return Instance.memHandle;
            }
        }

        public static byte ReadByte( int ptr )
        {
            return SMemory.ReadByte( MemHandle, ( uint ) ptr );
            //return b.ReadByte( ( uint ) ptr );
        }

        public static byte ReadBytePtr( int address, int offset )
        {
            var dir = ReadInt( address );
            return ReadByte( dir + offset );
        }

        public static int ReadInt( int ptr )
        {
            return SMemory.ReadInt( MemHandle, ( uint ) ptr );
            //return b.ReadInt( ( uint ) ptr );
        }

        public static uint ReadUInt( int ptr )
        {
            return ReadUInt( ( uint ) ptr );
        }

        public static uint ReadUInt( uint ptr )
        {
            return SMemory.ReadUInt( MemHandle, ptr );
        }

        public static int ReadIntPtr( int address, int offset )
        {
            var dir = ReadInt( address );
            return ReadInt( ( dir + offset ) );
        }

        public static uint ReadUIntPtr( int address, int offset )
        {
            var dir = ReadUInt( address );
            return ReadUInt( dir + ( uint ) offset );
        }

        public static uint ReadUIntPtr( int address, int[] offsets )
        {
            var dir = ReadUInt( address );
            //foreach ( var offset in offsets )
            //{
            //    dir = ReadUInt( dir + ( uint ) offset );
            //}
            //return dir;

            return offsets.Aggregate( dir, ( current, offset ) => ReadUInt( current + ( uint ) offset ) );
        }

        public static uint ReadUIntPtr( uint address, int[] offsets )
        {
            return ReadUIntPtr( ( int ) address, offsets );
        }

        public static float ReadFloat( int ptr )
        {
            return SMemory.ReadFloat( MemHandle, ( uint ) ptr );
        }

        public static string ReadASCIIString( uint ptr, int lenght = 24 )
        {
            return SMemory.ReadASCIIString( MemHandle, ptr, lenght );
        }

        public static string ReadASCIIString( int ptr, int lenght = 24 )
        {
            return ReadASCIIString( ( uint ) ptr, lenght );
        }

        public static string ReadString( uint ptr, int size = 24 )
        {
            return ReadASCIIString( ptr, size );
        }

        public static string ReadStringPtr( int address, int offset, int length = 24 )
        {
            var dir = ReadUInt( address );
            return ReadString( dir + ( uint ) offset, length );
        }

        public static string ReadUtf8String( uint ptr )
        {
            return SMemory.ReadUTF8String( MemHandle, ptr );
        }

        public static string ReadUtf8String( int ptr )
        {
            return ReadUtf8String( ( uint ) ptr );
        }

        public static string ReadUtf8StringPtr( uint address, uint ptr )
        {
            address = ReadUInt( address );
            return ReadUtf8String( address + ptr );
        }

        public static string ReadUtf8StringPtr( int address, int ptr )
        {
            return ReadUtf8StringPtr( ( uint ) address, ( uint ) ptr );
        }

        public static T ReadRepeat<T>( object proc, int address, int offset = -1 )
        {
            var def = default( T );
            for ( int i = 0; i < 10; i++ )
            {
                object val = null;
                if ( def is int )
                {
                    val = ReadInt( address );
                }
                else if ( def is uint )
                {
                    val = ReadUInt( address );
                }
                else if ( def is float )
                {
                    val = ReadFloat( address );
                }
                else if ( def is byte && offset >= 0 )
                {
                    val = ReadBytePtr( address, offset );
                }
                else if ( def is byte )
                {
                    val = ReadByte( address );
                }
                else if ( def is string )
                {
                    if ( offset < 0 )
                        offset = 0;
                    val = ReadASCIIString( address, offset );
                }
                else if ( def is IntPtr )
                {
                    if ( offset < 0 )
                        offset = 0;
                    val = ReadIntPtr( address, offset );
                }
                else if ( def is UIntPtr )
                {
                    if ( offset < 0 )
                        offset = 0;
                    val = ReadUIntPtr( address, offset );
                }


                if ( val != null )
                {
                    if ( !( val is string ) )
                    {
                        if ( val is float )
                        {
                            def = ( T ) val;
                            break;
                        }
                        long l;
                        if ( long.TryParse( val.ToString(), out l ) && l != 0 )
                        {
                            def = ( T ) val;
                            break;
                        }
                    }
                    else
                    {
                        def = ( T ) val;
                        break;
                    }
                }
            }

            return def;
        }

        public static T ReadRepeatPtr<T>( int address, int ptr )
        {
            var dir = ReadInt( address );
            return ReadRepeat<T>( Bot.Instancia.ProcId, dir + ptr );
        }

        public static T ReadRepeatPtr<T>( uint address, int ptr )
        {
            return ReadRepeatPtr<T>( ( int ) address, ptr );
        }

        public static bool WriteASCIIString( int ptr, string value )
        {
            return SMemory.WriteASCIIString( MemHandle, ( uint ) ptr, value );
        }

        public static bool WriteByte( int ptr, byte value )
        {
            return WriteByte( ( uint ) ptr, value );
        }

        public static bool WriteByte( uint ptr, byte value )
        {
            return SMemory.WriteByte( MemHandle, ptr, value );
        }

        public static bool WriteBytes( int ptr, byte[] value, int size )
        {
            return SMemory.WriteBytes( MemHandle, ( uint ) ptr, value, size );
        }

        public static bool WriteFloat( int ptr, float value )
        {
            return SMemory.WriteFloat( MemHandle, ( uint ) ptr, value );
        }

        public static bool WriteInt( uint address, int value )
        {
            return SMemory.WriteInt( MemHandle, address, value );
        }

        public static bool WriteInt( int address, int value )
        {
            return WriteInt( ( uint ) address, value );
        }

        public static IntPtr WindowHandle
        {
            get { return Instance.winHandle; }
        }

        public static HandleRef WindowHandleRef
        {
            get { return new HandleRef( Instance, Instance.winHandle ); }
        }

        public static IntPtr MemHandle
        {
            get { return ProcHandle; }
        }

        public static byte ReadUByte( long p )
        {
            return SMemory.ReadByte( MemHandle, ( uint ) p );
        }

        public static short ReadShort( long p )
        {
            return SMemory.ReadShort( MemHandle, ( uint ) p );
        }

        public static ushort ReadUShort( long p )
        {
            return SMemory.ReadUShort( MemHandle, ( uint ) p );
        }

        public static bool WriteString( int ptr, string val )
        {
            return WriteASCIIString( ptr, val );
        }

        public static bool WriteUnicodString( int ptr, string val )
        {
            return SMemory.WriteUnicodeString( MemHandle, ( uint ) ptr, val );
        }

        public static float ReadFloatPtr( int address, int offset )
        {
            var dir = ReadInt( address );
            return ReadFloat( dir + offset );
        }

        public static bool WriteBytePtr( int address, int ptr, byte val )
        {
            var dir = ReadInt( address );
            return WriteByte( ( uint ) ( dir + ptr ), val );
        }

        internal static float ReadFloatPtr( int address, int[] pointers )
        {
            var dir = ReadInt( address );
            dir = pointers.Aggregate( dir, ( current, pointer ) => ReadInt( current + pointer ) );
            return ReadFloat( dir );
        }

        public static void WriteFloatPtr( int address, int pointer, float value )
        {
            var dir = ReadInt( address );
            WriteFloat( dir + pointer, value );
        }

        internal static void WriteFloatPtr( int address, int[] pointers, float value )
        {
            var dir = ReadInt( address );
            dir = pointers.Aggregate( dir, ( current, pointer ) => ReadInt( current + pointer ) );
            WriteFloat( dir, value );
        }

        public static object ReadObject( int address, Type type )
        {
            return SMemory.ReadObject( MemHandle, ( uint ) address, type );
        }

        public static T ReadObject<T>( int address )
        {
            return SMemory.ReadObject<T>( MemHandle, ( uint ) address );
        }

        public static T[] ReadObjectArray<T>( int address, int arraySize )
        {
            return SMemory.ReadObjectArray<T>( MemHandle, ( uint ) address, arraySize );
        }
    }
}
